package triTree;

import triBasic.*;
import triCompact.*;
import triInstance.*;
import triEvent.*;
import transaction.*;
import utils.Constants;
import utils.FileOp;

import java.util.*;
import java.util.concurrent.locks.*;

public class TreeManager implements TriModule {
	
	private Vector<InsertItemEventHandler> insertItemHandlers;
	
	private Vector<RemoveItemEventHanlder> removeItemHandlers;
	
	private Vector<CreateNodeEventHandler> createNodeHandlers;
	
	private NodeManager nodeMgr;
	
	private ISetManager setMgr;
	
	private WritableTriNode root;
	
	private Object[] locks;
	
	private static int LOCK_NUM = 1000;
	
	private ReadWriteLock rwlock;
	private Lock rlock;
	private Lock wlock;
	
	
	public TreeManager() {
		this.insertItemHandlers = new Vector<InsertItemEventHandler>();
		this.removeItemHandlers = new Vector<RemoveItemEventHanlder>();
		this.createNodeHandlers = new Vector<CreateNodeEventHandler>();
		
		nodeMgr = new NodeManager(Constants.NODE_BUFFER_SIZE, 
				Constants.NODE_FILENAME, 
				Constants.NODE_PAGEBUFFER_SIZE, 
				Constants.NODE_PAGESIZE);
		
		setMgr = new SetManager(Constants.SET_BUFFER_SIZE, 
				Constants.SET_FILENAME,
				Constants.SET_PAGEBUFFER_SIZE, 
				Constants.SET_PAGESIZE,
				Constants.SET_INODE_FILENAME, 
				Constants.SET_INODE_PAGEBUFFER_SIZE,
				Constants.SET_INODE_PAGESIZE);
		
		rwlock = new ReentrantReadWriteLock();
		rlock = rwlock.readLock();
		wlock = rwlock.writeLock();
	}

	@Override
	public void init(Transaction tx) {
		nodeMgr.init(tx);
		setMgr.init(tx);
		root = nodeMgr.getRoot();
		
		locks = new Object[LOCK_NUM];
		for (int i = 0; i < LOCK_NUM; ++i)
			locks[i] = new Object();
	}
	
	public void compact(TxManager txMgr) {
		FileOp.deleteFile(Constants.SET_FILENAME_TEMP);
		FileOp.deleteFile(Constants.SET_INODE_FILENAME_TEMP);
		
		wlock.lock();

		CompSetMgr compSetMgr = new CompSetMgr(setMgr,
				Constants.SET_BUFFER_SIZE, 
				Constants.SET_FILENAME_TEMP,
				Constants.SET_PAGEBUFFER_SIZE, 
				Constants.SET_PAGESIZE,
				Constants.SET_INODE_FILENAME_TEMP, 
				Constants.SET_INODE_PAGEBUFFER_SIZE,
				Constants.SET_INODE_PAGESIZE);
		setMgr = compSetMgr;
		
		Transaction tx = txMgr.newTransaction();
		compSetMgr.init(tx);
		tx.commit();
		
		wlock.unlock();
		
		compSetMgr.compact(txMgr);
		
		wlock.lock();
		
		
		tx = txMgr.newTransaction();
		compSetMgr.dispose(tx);
		tx.commit();
		
		FileOp.renameFile(Constants.SET_FILENAME_TEMP, Constants.SET_FILENAME);
		FileOp.renameFile(Constants.SET_INODE_FILENAME_TEMP, Constants.SET_INODE_FILENAME);
		
		setMgr = new SetManager(Constants.SET_BUFFER_SIZE, 
				Constants.SET_FILENAME,
				Constants.SET_PAGEBUFFER_SIZE, 
				Constants.SET_PAGESIZE,
				Constants.SET_INODE_FILENAME,
				Constants.SET_INODE_PAGEBUFFER_SIZE,
				Constants.SET_INODE_PAGESIZE);
		
		tx = txMgr.newTransaction();
		setMgr.init(tx);
		tx.commit();
		
		wlock.unlock();
		
	}
	
	public void addInsertItemHandler(InsertItemEventHandler handler) {
		this.insertItemHandlers.add(handler);
	}
	
	public void addRemoveItemHandler(RemoveItemEventHanlder handler) {
		this.removeItemHandlers.add(handler);
	}
	
	public void addCreateNodeHandler(CreateNodeEventHandler handler) {
		this.createNodeHandlers.add(handler);
	}
	
	public boolean insertItem(TriItem item, TagList path, Transaction tx) {
		NodeSnapshot snapshot = getNodeSnapshot(path, true, tx);
		return insertItem(item, snapshot, tx);
	}
	
	public boolean insertItem(TriItem item, NodeSnapshot snapshot, Transaction tx) {
		if (snapshot == null || item == null || item.convert2String() == null)
			return false;
		
		rlock.lock();
		setMgr.insertItem(snapshot, item.convert2String(), tx);
		rlock.unlock();
		
		triggerInsertCB(item, snapshot, tx);
		return true;
	}

	public boolean removeItem(TriItem item, TagList path, Transaction tx) {
		NodeSnapshot snapshot = getNodeSnapshot(path, false, tx);
		return removeItem(item, snapshot, tx);
	}
	
	public boolean removeItem(TriItem item, NodeSnapshot snapshot, Transaction tx) {
		if (snapshot == null || item == null || item.convert2String() == null)
			return false;
		
		rlock.lock();
		setMgr.removeItem(snapshot, item.convert2String(), tx);
		rlock.unlock();
		
		triggerRemoveCB(item, snapshot, tx);
		return true;
	}
	
	public ISet<String> queryItemSet(TagList path, Transaction tx) {
		NodeSnapshot snapshot = getNodeSnapshot(path, false, tx);
		return this.queryItemSet(snapshot, tx);
	}
	
	public ISet<String> queryItemSet(NodeSnapshot snapshot, Transaction tx) {
		if (snapshot == null)
			return null;
		
		rlock.lock();
		ISet<String> set = setMgr.queryTriSet(snapshot, tx);
		rlock.unlock();
		return set;
	}
	
	public INode queryNode(TagList path, Transaction tx) {
		WritableTriNode node = getNode(path, false, tx);
		if (node == null)
			return null;
		
		INode ret = node.copy(tx);
		nodeMgr.unPin(node);
		
		return ret;
	}
	
	public INode queryNode(NodeSnapshot snapshot, Transaction tx) {
		WritableTriNode node = nodeMgr.getNode(snapshot, tx);
		INode ret = node.copy(tx);
		nodeMgr.unPin(node);
		return ret;
	}
		
	private void triggerInsertCB(TriItem item, NodeSnapshot snapshot, Transaction tx) {
		for (int i = 0; i < insertItemHandlers.size(); ++i)
			insertItemHandlers.elementAt(i).handleInsertItem(item, snapshot, tx);
	}
	
	private void triggerRemoveCB(TriItem item, NodeSnapshot snapshot, Transaction tx) {
		for (int i = 0; i < removeItemHandlers.size(); ++i)
			removeItemHandlers.elementAt(i).handleRemoveItem(item, snapshot, tx);
	}
	
	private void triggerCreateCB(NodeSnapshot snapshot, INode node, Transaction tx) {
		for (int i = 0; i < createNodeHandlers.size(); ++i)
			createNodeHandlers.elementAt(i).handleCreateNode(snapshot, node, tx);
	}
	
	private WritableTriNode getNode(TagList path, boolean create, Transaction tx) {
		return nodeMgr.getNode(getNodeSnapshot(path, create, tx), tx);
	}
	
	private NodeSnapshot getNodeSnapshot(TagList path, boolean create,  Transaction tx) {		
		path.resetPos();
		String tag;
		NodeSnapshot snapshot = root.getNodeSnapshot();
		while (true) {
			tag = path.nextTag();
			if (tag == null)
				break;
			//System.out.println(tag);
			
			snapshot = getOrCreateChild(snapshot, tag, create, tx);
		}
		
		return snapshot;
	}
	
	private NodeSnapshot getOrCreateChild(NodeSnapshot faSnapshot, String tagName, boolean create,
			 Transaction tx) {
		//another lock
		synchronized (locks[faSnapshot.getNodeId() % LOCK_NUM]) {
			WritableTriNode father = nodeMgr.getNode(faSnapshot, tx);
			NodeSnapshot snapshot = father.findChild(tagName, tx);
			
			if (snapshot == null && !create)
				return null;
			else if (snapshot == null && create) {
				rlock.lock();
				int setInodePageId = setMgr.createNewSet();
				System.out.println("create " + setInodePageId);
				rlock.unlock();
				
				snapshot = nodeMgr.createNewNode(father, tagName, setInodePageId, tx);
				triggerCreateCB(snapshot, queryNode(snapshot, tx), tx);
			}
			
			nodeMgr.unPin(father);
			return snapshot;
		}
	}
	
	public void dispose(Transaction tx) {
		nodeMgr.dispose(tx);
		setMgr.dispose(tx);
	}
	
}

